/* Copyright (c) 2009-2010, Sean A.O. Harney <sharney3@gmail.com> */

#include "omf.h"



extern char *strsignal(int sig);

/* prototypes */
void usage(char *arg0);
void sigchld_handler(int signum);
void sigint_handler(int signum);
void print_alert_flagged(char *msg);
void print_source(FILE * fp, const char *fname);
bool init_glob(void);

int main(int argc, char *argv[])
{
    int c;
    char *l_opt = NULL;
    int num_dflag = 0;
    bool quiet = false;
    bool outputlog = true;	//default is to create the outputlog dir (but not until omf run-time, 1st start stmt)
    bool parseonly = false;
    bool print_source_out = false;
    FILE *yyin_fp = stdin;	//default
    char *outputlog_progname = PACKAGE_NAME;	/* from Makefile */


    if (!init_glob())
	exit(EXIT_FAILURE);

    while ((c = getopt(argc, argv, "hVdl:qNEP")) != -1)	//p requires argument
	switch (c)
	  {
	  case 'h':
	      usage(argv[0]);
	      exit(EXIT_SUCCESS);
	  case 'V':		/* print version on stdout */
	      printf("%s\n", VERSION);
	      exit(EXIT_SUCCESS);
	  case 'd':
	      num_dflag++;
	      break;
	  case 'q':
	      quiet = true;
	      break;
	  case 'l':
	      l_opt = checked_strdup(optarg);
	      break;
	  case 'N':		//--no-outputdir
	      outputlog = false;
	      break;
	  case 'E':
	      print_source_out = true;
	      break;
	  case 'P':
	      parseonly = true;
	      break;
	  default:
	      usage(argv[0]);
	      exit(EXIT_FAILURE);
	  }

    if (optind < argc - 1)	//more than one additional arg
      {
	  usage(argv[0]);
	  exit(EXIT_FAILURE);
      }
    // NOTE: optind won't ever be greater than argc - 1
    if (optind == argc - 1)	// if exactly one argument after flags
      {
	  char *fname = argv[optind];

	  /* needed for #!/path/to/ofg to work in scripts */
	  if ((yyin_fp = fopen(fname, "r")) == NULL)
	      fatal_error_perror(fname);

          glob.parser_current_filename = fname;
	  outputlog_progname = fname;	/* no longer using default progname, now using file name */

	  /* no longer using the default yyin=stdin, now using the file */
	  if (print_source_out == true)
	      print_source(yyin_fp, fname);
      }


    if (quiet == true)		// ignore any -d flags and -l opts given.
      {
	  debug_init(DEBUG_LEVEL_QUIET, NULL);
    } else
      {
	  debug_init(num_dflag, l_opt);	//l_opt will be NULL if not set
      }


// change process group:
    setpgid(0, 0);		// change process group to same number as this pid

    struct sigaction sa;
    sa.sa_handler = sigchld_handler;
    sa.sa_flags = 0;
    sigemptyset(&sa.sa_mask);
    sigaction(SIGCHLD, &sa, NULL);

    sa.sa_handler = sigint_handler;
    sa.sa_flags = 0;
    sigemptyset(&sa.sa_mask);
    sigaction(SIGINT, &sa, NULL);

    signal(SIGPIPE, SIG_IGN);


    if (!parse_init(yyin_fp, parseonly))
	exit(EXIT_FAILURE);

    /* doesn't get here until finished parsing (successfully) */

	if (parseonly)
	      exit(EXIT_SUCCESS);
	else if(debug_level == 1) //&& !parseonly
		debug_unquiet_printf("Use debug_level >=2 to print out ASL(s).\n");


   
    /* create OMFoutputlog dir and symlink */
    outputlog_init(outputlog, outputlog_progname);
/* execute `main` function (`main` ASL) */
    asl_process_main();

    debug_unquiet_printf
	("Finished Program. Waiting for %d target child processes to die.\n",
	 glob.current_target_processes);

    while (glob.current_target_processes > 0)
	sleep(1000);


    exit(EXIT_SUCCESS);
}

//hVdl:qNP
void usage(char *arg0)
{
    printf("%s %s\n\n"
	   "Usage:\t%s [ -hVqNEP ] [ -d | -dd ] [ -l <debug log file> ] [ <omf script file> ]\n\n"
	   "\t[ -h ]\t\t\tPrint this usage help to stdout.\n"
	   "\t[ -V ]\t\t\tPrint version information to stdout.\n"
	   "\t[ -q ]\t\t\tQuiet mode.\n"
	   "\t[ -N ]\t\t\tDo not create OMFoutputlog directory or symlink.\n"
	   "\t[ -E ]\t\t\tPrint source to stdout, continue normally.\n"
	   "\t[ -P ]\t\t\tParse, build and print out ASL(s), exit. Implies Debug Level 2.\n"
	   "\t[ -d ]\t\t\tIncrease Debug Level by 1 for each -d flag used.\n"
	   "\t[ -l <debug log file> ]\tOutput debug to file. Default stderr if not specified.\n"
	   "\t[ -l <debug log file> ]\tOutput debug to file. Default stderr if not specified.\n"
	   "\t[ <omf script file> ]\tInput omf script file. Default stdin if not specified.\n"
	   "\nCopyright (c) 2009-2010, Sean A.O. Harney <sharney3@gmail.com>\n\n",
	   PACKAGE_NAME, VERSION, arg0);
}


void sigchld_handler(int signum)
{

    pid_t cpid;
    int status;
    char msg[512];
    struct sigaction sa;
    bool alert_flagged = false;
    struct st_vm_flag_state *flag_state = glob.vm_state.flag_state;

    sa.sa_handler = SIG_IGN;
    sa.sa_flags = 0;
    sigemptyset(&sa.sa_mask);
    sigaction(SIGCHLD, &sa, NULL);


    debug2_printf("start of sigchld_hander\n");
    //All child process (zombies). If none then don't block,but there should be one
    // since inside sigchld_handler
    while ((cpid = waitpid(-1, &status, WNOHANG)) > 0)
      {
	  if (cpid == regldg_pid)
	      continue;		//to prevent it from printing out that it terminated the regldg pid.
	  if (regldg_pid == 0)	//NOTE: fastrunning regldg_pid might die before global set.
	      continue;

	  glob.current_target_processes--;
	  if (WIFEXITED(status))	// terminated normally ie exit()
	    {
		int exit_status = WEXITSTATUS(status);
		snprintf(msg, sizeof(msg),
			 "Terminated pid=%d, exit status: %d\n", cpid,
			 exit_status);

		if (flag_state->termination_any
		    || flag_state->termination_exit_any
		    || llist_int_contains(exit_status,
					  flag_state->termination_exits))
		  {
		      alert_flagged = true;
		  }

	  } else if (WIFSIGNALED(status))	// killed by a signal
	    {
		int signum = WTERMSIG(status);
		snprintf(msg, sizeof(msg),
			 "Terminated pid=%d [signal %d: %s]\n", cpid,
			 signum, strsignal(signum));

		if (flag_state->termination_any
		    || flag_state->termination_signal_any
		    || llist_int_contains(signum,
					  flag_state->termination_signals))
		  {
		      alert_flagged = true;
		  }
	    }

	  debug1_printf("%s", msg);

	  if (alert_flagged)
	      print_alert_flagged(msg);

	  //resolve directory name from cpid
	  char *dirname = outputlog_get_dirname(cpid);


	  if (dirname != NULL)	// only target processes and not regdlg process wwill have a subdirs.txt entry from get_dirname()
	    {
		//TODO:error check, but not fatal.
		outputlog_create_termination_log(dirname, msg);
		if (alert_flagged)
		  {
		      //TODO: if mail alerts
		      //mail_alert(dirname, msg);
		  }


		free(dirname);
	    }			//else didn't find it. OR outputlog_active=false


      }

    debug2_printf("end of sigchld_hander: current_target_processes=%d\n",
		  glob.current_target_processes);

/* restore sigchld_handler */
    sa.sa_handler = sigchld_handler;
    sa.sa_flags = 0;
    sigemptyset(&sa.sa_mask);
    sigaction(SIGCHLD, &sa, NULL);


}

void sigint_handler(int signum)
{
    printf("\nCAUGHT ^C !!\n");

// here it pays to have a global reference to it.
    if (glob.vm_state.perm_count != 0)	// interrupted the perms loop
	printf("\tCompleted only %ld of ??? permutations\n",
	       glob.vm_state.perm_count);

    signal(SIGTERM, SIG_IGN);
    //kill all process group, but this, we want it to use the registered atexit, which won't happen if it is sigtermed.
    kill(0, SIGTERM);
    signal(SIGTERM, SIG_DFL);	// restore SIGTERM

    exit(EXIT_SUCCESS);
}

bool init_glob()
{
    if (gettimeofday(&(glob.omf_starttime), NULL) < 0)
      {
	  perror("gettimeofday()");
	  return false;
      }
    glob.omf_pid = getpid();
    glob.current_target_processes = 0;	//reset this
    glob.parser_num_errors = 0;
    glob.parser_current_filename = NULL;
    glob.parser_current_is_include = false;
    glob.parser_explicit_main = false;
    glob.validation_mode = false;
    glob.vm_state.flag_state = NULL;	//needs to be NULL for fist vm_state_reset.
    vm_state_reset();		// reset glob.vm_state
    return true;
}

//reset glob.vm_state
void vm_state_reset(void)
{
    struct st_vm_state *vm_state = &(glob.vm_state);
    struct st_vm_flag_state *flag_state;

    //randomhhandle = ? //TODO
    vm_state->perm_count = 0;
    vm_state->fuzzdata_state = NULL;	//don't free, were ptrs assigned elsewhere.
    vm_state->target_state = NULL;

    if (vm_state->flag_state != NULL)
	free(vm_state->flag_state);
    flag_state = vm_state->flag_state =
	checked_malloc(sizeof(struct st_vm_flag_state));
    flag_state->termination_any = false;
    flag_state->termination_signal_any = false;
    flag_state->termination_exit_any = false;
    flag_state->termination_signals = NULL;
    flag_state->termination_exits = NULL;
}

/* expecting msg to be a C string terminated with \n\0 */
void print_alert_flagged(char *msg)
{
//TODO: check if works with -quiet
    char alert_msg_str[2046];

    snprintf(alert_msg_str, sizeof(alert_msg_str),
	     "\n!!!!!!!!! ALERT! Flagged Event Occured !!!!!!!!!!\n"
	     "!\t%s"
	     "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n", msg);

    if (debug_file != NULL && debug_file != stderr)
	debug_unquiet_printf("%s", alert_msg_str);


    fprintf(stderr, "%s", alert_msg_str);	// always to stderr
    fflush(stderr);
}


//Assumes fp is aready opened and rewound.
//At return, fp is rewind() (rewound) here
void print_source(FILE * fp, const char *fname)
{
#define PS_CHUNK_SIZE 24
    char psbuf[PS_CHUNK_SIZE];
    size_t rr;
    const char *fmt_bgn =
	"|----------------- PRINT SOURCE BEGIN: %s ----------------->\n";
    const char *fmt_end =
	"<------------------ PRINT SOURCE END: %s ------------------|\n";

    printf(fmt_bgn, fname);
    while (1)
      {
	  rr = fread(psbuf, 1, PS_CHUNK_SIZE, fp);	/* -1 for the \0 */
	  //      printf("rr = %d\n", rr);
	  if (rr == 0)		/* error or EOF */
	    {
		if (ferror(fp) != 0)	/* error indicator is set , does not modify errno */
		  {
		      /* doesn't need to be a fatal error */
		      perror(fname);
		      //clearerr(fp); /* clear error indicator.NOT needed, rewind below does this (fseek doesn't). */
		  }
		break;
	    }
	  psbuf[rr] = '\0';	/* just in case. assuming its an ascii file anyway */
	  printf("%s", psbuf);
      }				/* end while */

    printf(fmt_end, fname);
    fflush(stdout);
    rewind(fp);			/* rewind and clear error indicator before parsing ! */
}
